package genJvn;

import java.io.File;
import java.io.FileOutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.List;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import jvn.client.annotation.ReadOnly;
import jvn.client.annotation.SharedObject;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;

public class genJvn {
	final static private Logger logger = Logger.getLogger(genJvn.class);

	final static private String jvnPrefix = "Jvn_";

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		BasicConfigurator.configure();

		System.out.println("Generating intercepting classes.");

		File[] fileArray;
		fileArray = new File[args.length];
		for (int i = 0; i < args.length; i++) {
			String arg = args[i];
			String packageName = arg.substring(0, arg.lastIndexOf('.'));
			String className = arg.substring(arg.lastIndexOf('.') + 1, arg.length());

			System.out.println("Generating " + className);

			try {
				Writer stringWriter = runVelocity(packageName, className);
				stringWriter.close();

				File f = new File("src/" + packageName.replace('.', File.separatorChar) + File.separatorChar
						+ jvnPrefix + className + ".java");
				f.createNewFile();
				new FileOutputStream(f).write(stringWriter.toString().getBytes());

				fileArray[i] = new File("src/" + packageName + File.separatorChar + jvnPrefix + className + ".java");

			} catch (ClassNotFoundException e1) {
				logger.warn("Couldn't find the class", e1);
			} catch (ResourceNotFoundException rnfe) {
				logger.warn("Couldn't find the template", rnfe);
			} catch (ParseErrorException pee) {
				logger.warn("Syntax error: problem parsing the template", pee);
			} catch (MethodInvocationException mie) {
				logger.warn("Something invoked in the template  threw an exception", mie);
			} catch (Exception e) {
				logger.warn(e);
			}
		}

		System.out.println("Compilling classes");

		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);

		Iterable<? extends JavaFileObject> compilationUnits1 = fileManager.getJavaFileObjectsFromFiles(Arrays
				.asList(fileArray));
		compiler.getTask(null, fileManager, null, null, null, compilationUnits1).call();

	}

	private static Writer runVelocity(String packageName, String className) throws ClassNotFoundException,
			ResourceNotFoundException, ParseErrorException, MethodInvocationException {
		Writer sw = new StringWriter();
		runVelocity(packageName, className, sw);
		return sw;
	}

	public static/* StringWriter */void runVelocity(String packageName, String className, Writer sw)
			throws ClassNotFoundException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {

		Class<?> sharedObject = Class.forName(packageName + "." + className);

		Velocity.init();
		VelocityContext context = new VelocityContext();

		String objectName = className;

		context.put("package", packageName);

		context.put("class", objectName);

		List<String> imports = new ArrayList<>();
		imports.add(objectName);
		context.put("imports", sharedObject.getName());

		SharedObject sharedObjectAnno = sharedObject.getAnnotation(SharedObject.class);
		String sharedObjectName = objectName;
		if (sharedObjectAnno != null) {
			sharedObjectName = sharedObjectAnno.value();
		}

		boolean trouve = false;
		for (Class<?> c : sharedObject.getInterfaces()) {
			if (c.getName().contains("java.io.Serializable")) {
				trouve = true;
			}
		}
		if (!trouve) {
			logger.error("The class " + objectName + " do not implements java.io.Serializable");
			System.exit(1);
		}

		context.put("sharedObjectName", sharedObjectName);

		List<Constructeur> constructeurs = new ArrayList<>();
		for (Constructor<?> currentConstructor : sharedObject.getDeclaredConstructors()) {
			List<String> params = new ArrayList<>();

			for (Class<?> constr : currentConstructor.getParameterTypes()) {
				params.add(constr.getName());
			}

			String entete = currentConstructor.toString();
			// tostring : public boolean java.lang.Object.equals(java.lang.Object) throws bla

			String baseMethode = entete.split("\\(")[0];
			String exceptions = entete.split("\\)", 2)[1];

			String[] a = baseMethode.split(" ");

			int i = baseMethode.lastIndexOf(" ");
			String[] b = baseMethode.substring(i, baseMethode.length()).split("\\.");
			baseMethode = baseMethode.substring(0, i);

			String nom = currentConstructor.getName();
			// String retour = currentConstructor.getReturnType().toString();
			// imports.add(retour);

			// String[] temp2 = retour.split("\\.");
			// retour = temp2[temp2.length - 1];

			boolean readOnly = currentConstructor.isAnnotationPresent(ReadOnly.class);
			
			if (!currentConstructor.toString().contains("JvnException")) {
				logger.error("The method " + currentConstructor.toString() + " do not throw a jvn.client.JvnException");
				System.exit(1);
			}
			
			constructeurs.add(new Constructeur(baseMethode, exceptions, params));

		}

		context.put("constructeurs", constructeurs);

		List<Methode> methods = new ArrayList<>();
		// for (Method m : sharedObject.getDeclaredMethods()) {
		for (Method m : sharedObject.getMethods()) {

			if (m.getDeclaringClass() != Object.class) {
				List<String> params = new ArrayList<>();

				for (Class<?> param : m.getParameterTypes()) {
					params.add(param.getName());
				}

				String entete = m.toString();
				// tostring : public boolean java.lang.Object.equals(java.lang.Object) throws bla

				String baseMethode = entete.split("\\(")[0];
				String exceptions = entete.split("\\)", 2)[1];

				String[] a = baseMethode.split(" ");

				int i = baseMethode.lastIndexOf(" ");
				String[] b = baseMethode.substring(i, baseMethode.length()).split("\\.");
				baseMethode = baseMethode.substring(0, i) + " " + b[b.length - 1];

				String nom = m.getName();
				String retour = m.getReturnType().toString();
				// imports.add(retour);

				String[] temp2 = retour.split(" ");
				if (temp2.length == 1) {
					retour = temp2[0];
				} else {
					retour = temp2[1];
				}

				boolean readOnly = m.isAnnotationPresent(ReadOnly.class);

				if (!exceptions.contains("JvnException")) {
					logger.error("The method " + m.toString() + " do not throw a jvn.client.JvnException");
					System.exit(1);
				}

				methods.add(new Methode(baseMethode, exceptions, params, nom, retour, readOnly));
			}
		}

		context.put("methods", methods);

		Template template = null;

		template = Velocity.getTemplate("src/genJvn/mytemplate.vm");
		if (template == null) {
			throw new ResourceNotFoundException("Template non trouvé");
		}

		// StringWriter sw = new StringWriter();

		template.merge(context, sw);

		// return sw;
	}

}
